﻿#region copyright
//                Copyright Andrew Rafas 2012.
// Distributed under the Eclipse Public License, Version 1.0.
//           (See accompanying file LICENSE.txt or 
//     copy at http://www.eclipse.org/legal/epl-v10.html)
#endregion
using System;
using System.Collections.Generic;

namespace CSFP.Memoize
{
    public interface ISource
    {
        /// <summary>
        /// Returns true if the value can be calculated, or already has been calculated.
        /// When returns false, Value will throw on access (if we are an IValue[T]).
        /// </summary>
        bool IsReady { get; }
        void AddDependent(IDependent dependency, int key);
    }

    public interface IValue<T> : ISource
    {
        /// <summary>
        /// Returns the expression's value when IsReady == true. It either returns the cached 
        /// value or calculates (and possibly caches) a fresh one.
        /// </summary>
        T Value { get; }
    }

    public interface IDependent
    {
        void Notify(int readyChange, int key);
    }

    public abstract class DependentList
    {
        // we can just substract these values from _notReadyCount
        public const int BecomeNotReady = -1; // which implies that all dependencies has to invalidate the cached value
        public const int ValueChanged = 0; // was Ready and remained Ready but all dependencies has to invalidate the cached value
        public const int BecomeReady = +1; // which implies that all dependencies has to invalidate the cached value

        public abstract void NotifyAll(int readyChange);
        public abstract DependentList Add(DepKey dk);
    }

    public struct DepKey
    {
        public readonly IDependent Item1;
        public readonly int Item2;

        public DepKey(IDependent item1, int item2)
        {
            Item1 = item1;
            Item2 = item2;
        }
    }

    public class MultiDependentList : DependentList
    {
        DepKey[] _list = new DepKey[4];
        int _count;

        public override void NotifyAll(int readyChange)
        {
            var list = _list;
            for (int i = 0; i < _count; ++i) {
                var d = list[i];
                d.Item1.Notify(readyChange, d.Item2);
            }
        }

        public override DependentList Add(DepKey dk)
        {
            if (_count >= _list.Length) {
                var newlist = new DepKey[_list.Length * 2];
                Array.Copy(_list, newlist, _list.Length);
                _list = newlist;
            }
            _list[_count] = dk;
            ++_count;
            return this;
        }
    }

    public class SingleDependentList : DependentList
    {
        DepKey d;

        public SingleDependentList(DepKey d)
        {
            this.d = d;
        }

        public override void NotifyAll(int readyChange)
        {
            d.Item1.Notify(readyChange, d.Item2);
        }

        public override DependentList Add(DepKey dk)
        {
            var l = new MultiDependentList();
            l.Add(d);
            return l.Add(dk);
        }
    }

    public class EmptyDependentList : DependentList
    {
        public static EmptyDependentList Instance = new EmptyDependentList();

        public override void NotifyAll(int readyChange)
        {
        }

        public override DependentList Add(DepKey dk)
        {
            return new SingleDependentList(dk);
        }
    }

    public static class Helper
    {
#if DEBUG
        static Dictionary<object, string> _debugInfo = new Dictionary<object, string>();
        static Dictionary<object, string> _shortDebugInfo = new Dictionary<object, string>();
        public static long CalculationCount = 0;
        public static long NotificationCount = 0;

        static string debugize(object o, object def)
        {
            if (def == null)
                def = o;
            string info;
            if (_shortDebugInfo.TryGetValue(o, out info))
                return info;
            if (_debugInfo.TryGetValue(o, out info))
                return info;
            return def.ToString();
        }

        [System.Diagnostics.Conditional("DEBUG")]
        internal static void Log(string format, params object[] values)
        {
            for (int i = 0; i < values.Length; ++i) {
                string info;
                if (_debugInfo.TryGetValue(values[i], out info))
                    values[i] = info;
            }
            string line = string.Format(format, values);
            System.Diagnostics.Trace.WriteLine(line);
        }

        /// <summary>
        /// Sets up the debuginfo which is used for tracing calculations.
        /// </summary>
        [System.Diagnostics.Conditional("DEBUG")]
        public static void Debug(object o, string info, string shortName = null)
        {
            if (o is Dependent) {
                var d = (Dependent)o;
                var sb = new System.Text.StringBuilder();
                sb.AppendFormat("{0}(", info);
                for (int i = 0; i < d.SourceList.Length; ++i) {
                    if (i != 0)
                        sb.Append(", ");
                    sb.Append(debugize(d.SourceList[i], "?"));
                }
                sb.Append(")");
                _debugInfo[o] = sb.ToString();
            } else {
                _debugInfo[o] = info;
            }
            if (shortName != null)
                _shortDebugInfo[o] = shortName;
        }
#else
        public static void Debug(object o, string info, string shortName = null) { }
#endif
    }
}
